package util

//go:generate genny -in=pubsub_template.go -out=gen_pubsub.go gen "PubSubValue=*model.Room,*model.PlayerState,model.TimeDuration,*models.RoomListUpdatedRes"

import (
	"sync"

	"github.com/cheekybits/genny/generic"
)

// Helper Structure for Pubsub Model

// PubSubValue contains value to send to channel
type PubSubValue generic.Type

type intMapChanPubSubValue map[int]*PubSubValueRingBuffer

// IPubSubValuePubsub defines the behaviour
type IPubSubValuePubsub interface {
	// Subscribe add channel to id
	Subscribe(id int) <-chan PubSubValue
	// Unsubscribe delete channel to id
	Unsubscribe(id int)
	// UnsubscribeAll delets all channel
	UnsubscribeAll()
	// Publish the same value to all id
	Publish(value PubSubValue)
	// Get channel according to id, second is ok
	Get(id int) (<-chan PubSubValue, bool)
	// Return the size of subscriber
	Size() int
}

// PubSubValuePubSub creates pubsub model
type PubSubValuePubSub struct {
	clientMapChannel intMapChanPubSubValue
	mtx              *sync.RWMutex
	// bufsize the channel bufsize
	bufsize int
}

// NewPubSubValuePubSub creates PubSubValuePubSub
func NewPubSubValuePubSub() *PubSubValuePubSub {
	return &PubSubValuePubSub{
		clientMapChannel: make(intMapChanPubSubValue),
		mtx:              &sync.RWMutex{},
		bufsize:          2,
	}
}

// Subscribe add channel to id
func (p *PubSubValuePubSub) Subscribe(id int) <-chan PubSubValue {
	ringBuffer := NewPubSubValueRingBuffer()
	outCh := ringBuffer.GetOutputChannel()
	p.mtx.Lock()
	defer p.mtx.Unlock()
	p.clientMapChannel[id] = ringBuffer
	return outCh
}

// Unsubscribe delete channel to id
func (p *PubSubValuePubSub) Unsubscribe(id int) {
	p.mtx.Lock()
	defer p.mtx.Unlock()
	// close channel
	rb, ok := p.clientMapChannel[id]
	if ok {
		rb.Close()
		delete(p.clientMapChannel, id)
	}
}

// UnsubscribeAll delets all channel
func (p *PubSubValuePubSub) UnsubscribeAll() {
	p.mtx.Lock()
	defer p.mtx.Unlock()
	for _, rb := range p.clientMapChannel {
		rb.Close()
	}
	// remake a map
	p.clientMapChannel = make(intMapChanPubSubValue)
}

// Publish the same value to all id
func (p *PubSubValuePubSub) Publish(value PubSubValue) {
	p.mtx.RLock()
	defer p.mtx.RUnlock()
	for _, rb := range p.clientMapChannel {
		// use  ringbuffer channel
		rb.Send(value)
	}
}

// Get channel according to id, second is ok
func (p *PubSubValuePubSub) Get(id int) (<-chan PubSubValue, bool) {
	p.mtx.RLock()
	defer p.mtx.RUnlock()
	rb, ok := p.clientMapChannel[id]
	if !ok {
		return nil, false
	}
	return rb.GetOutputChannel(), true
}

// Size returns the size of subscriber
func (p *PubSubValuePubSub) Size() int {
	return len(p.clientMapChannel)
}
